package org.wevil.util;

/**
 * CRC16 相关
 *
 * @author linanguang
 * @since 2020-05-15 10:58
 */
public class CRC16Util {

    /**
     * CRC16_CCITT：多项式x16+x12+x5+1（0x1021），初始值0x0000，低位在前，高位在后，结果与0x0000异或
     * 0x8408是0x1021按位颠倒后的结果。
     *
     * @param buffer
     * @return
     */
    public static int CRC16_CCITT(byte[] buffer) {
        int wCRCin = 0x0000;
        int wCPoly = 0x8408;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
//        wCRCin=(wCRCin<<8)|(wCRCin>>8);
//        wCRCin &= 0xffff;
        return wCRCin ^= 0x0000;

    }

    /**
     * CRC-CCITT (0xFFFF)
     * CRC16_CCITT_FALSE：多项式x16+x12+x5+1（0x1021），初始值0xFFFF，低位在后，高位在前，结果与0x0000异或
     *
     * @param buffer
     * @return
     */
    public static int CRC16_CCITT_FALSE(byte[] buffer) {
        int wCRCin = 0xffff;
        int wCPoly = 0x1021;
        for (byte b : buffer) {
            for (int i = 0; i < 8; i++) {
                boolean bit = ((b >> (7 - i) & 1) == 1);
                boolean c15 = ((wCRCin >> 15 & 1) == 1);
                wCRCin <<= 1;
                if (c15 ^ bit) {
                    wCRCin ^= wCPoly;
                }
            }
        }
        wCRCin &= 0xffff;
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC-CCITT (XModem)
     * CRC16_XMODEM：多项式x16+x12+x5+1（0x1021），初始值0x0000，低位在后，高位在前，结果与0x0000异或
     *
     * @param buffer
     * @return
     */
    public static int CRC16_XMODEM(byte[] buffer) {
        int wCRCin = 0x0000; // initial value 65535
        int wCPoly = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
        for (byte b : buffer) {
            for (int i = 0; i < 8; i++) {
                boolean bit = ((b >> (7 - i) & 1) == 1);
                boolean c15 = ((wCRCin >> 15 & 1) == 1);
                wCRCin <<= 1;
                if (c15 ^ bit) {
                    wCRCin ^= wCPoly;
                }
            }
        }
        wCRCin &= 0xffff;
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC-AUG-CCITT
     * CRC16_AUG-CCITT：多项式x16+x12+x5+1（0x1021），初始值0x1D0F，低位在后，高位在前，结果与0x0000异或
     *
     * @param buffer
     * @return
     */
    public static int CRC16_AUG_CCITT(byte[] buffer) {
        int wCRCin = 0x1D0F; // initial value 65535
        int wCPoly = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
        for (byte b : buffer) {
            for (int i = 0; i < 8; i++) {
                boolean bit = ((b >> (7 - i) & 1) == 1);
                boolean c15 = ((wCRCin >> 15 & 1) == 1);
                wCRCin <<= 1;
                if (c15 ^ bit) {
                    wCRCin ^= wCPoly;
                }
            }
        }
        wCRCin &= 0xffff;
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC16_X25：多项式x16+x12+x5+1（0x1021），初始值0xffff，低位在前，高位在后，结果与0xFFFF异或
     * 0x8408是0x1021按位颠倒后的结果。
     *
     * @param buffer
     * @return
     */
    public static int CRC16_X25(byte[] buffer) {
        int wCRCin = 0xffff;
        int wCPoly = 0x8408;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0xffff;
    }

    /**
     * CRC-16 (Modbus)
     * CRC16_MODBUS：多项式x16+x15+x2+1（0x8005），初始值0xFFFF，低位在前，高位在后，结果与0x0000异或
     * 0xA001是0x8005按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_MODBUS(byte[] buffer) {
        int wCRCin = 0xffff;
        int POLYNOMIAL = 0xa001;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= POLYNOMIAL;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC-16
     * CRC16_IBM：多项式x16+x15+x2+1（0x8005），初始值0x0000，低位在前，高位在后，结果与0x0000异或
     * 0xA001是0x8005按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_IBM(byte[] buffer) {
        int wCRCin = 0x0000;
        int wCPoly = 0xa001;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC16_MAXIM：多项式x16+x15+x2+1（0x8005），初始值0x0000，低位在前，高位在后，结果与0xFFFF异或
     * 0xA001是0x8005按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_MAXIM(byte[] buffer) {
        int wCRCin = 0x0000;
        int wCPoly = 0xa001;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0xffff;
    }

    /**
     * CRC16_OB：多项式x16+x2+x1+1（0x0006），初始值0x199A，低位在前，高位在后，结果与0x0000异或
     * 0x6000是0x0006按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_OB(byte[] buffer) {
        int wCRCin = 0x199A;
        int wCPoly = 0x6000;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0x0000;
    }

    /**
     * CRC16_USB：多项式x16+x15+x2+1（0x8005），初始值0xFFFF，低位在前，高位在后，结果与0xFFFF异或
     * 0xA001是0x8005按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_USB(byte[] buffer) {
        int wCRCin = 0xFFFF;
        int wCPoly = 0xa001;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0xffff;
    }

    /**
     * CRC16_DNP：多项式x16+x13+x12+x11+x10+x8+x6+x5+x2+1（0x3D65），初始值0x0000，低位在前，高位在后，结果与0xFFFF异或
     * 0xA6BC是0x3D65按位颠倒后的结果
     *
     * @param buffer
     * @return
     */
    public static int CRC16_DNP(byte[] buffer) {
        int wCRCin = 0x0000;
        int wCPoly = 0xA6BC;
        for (byte b : buffer) {
            wCRCin ^= ((int) b & 0x00ff);
            for (int j = 0; j < 8; j++) {
                if ((wCRCin & 0x0001) != 0) {
                    wCRCin >>= 1;
                    wCRCin ^= wCPoly;
                } else {
                    wCRCin >>= 1;
                }
            }
        }
        return wCRCin ^= 0xffff;
    }


    /**
     * 将十进制整数转化为两个字节存储到字节数组中（大端）
     * 用于辅助生成CRC校验码
     *
     * @param data
     * @return
     */
    public static byte[] intTo2Byte(int data) {
        byte[] byteArray = new byte[2];
        byteArray[0] = (byte) (data >> 8);
        byteArray[1] = (byte) data;
        return byteArray;
    }

    /**
     * 用于建立十六进制字符的输出的小写字符数组
     */
    private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * 用于建立十六进制字符的输出的大写字符数组
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};


    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data byte[]
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data) {
        return encodeHexStr(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data        byte[]
     * @param toLowerCase <code>true</code> 传换成小写格式 ， <code>false</code> 传换成大写格式
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data, boolean toLowerCase) {
        return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制String
     */
    protected static String encodeHexStr(byte[] data, char[] toDigits) {
        return new String(encodeHex(data, toDigits));
    }


    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制char[]
     */
    protected static char[] encodeHex(byte[] data, char[] toDigits) {
        int l = data.length;
        char[] out = new char[l << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    /**
     * 计算CRC16 校验
     * 根据int数组计算CRC16 校验结果（返回10进制数原始报文 + crc16校验的低八位和高八位）
     * 返回结果 原始数据 + 追加CRC16 校验结果
     *
     * @author linanguang
     * @since 2020-05-15 10:58
     */
    public static int[] getCrc16(int[] data) {
        // 传入四个个十六进制的十进制数,传出六个十六进制的十进制数,后两个就是crc16校验的低八位和高八位
        int[] crcResult = new int[data.length + 2];
        // unsigned char alen = *aStr – 2;
        // CRC16只计算前两部分
        int xda, xdapoly;
        int i, j, xdabit;
        xda = 0xFFFF;
        // (X**16 + X**15 + X**2 + 1)
        xdapoly = 0xA001;
        for (i = 0; i < data.length; i++) {
            xda ^= data[i];
            for (j = 0; j < 8; j++) {
                xdabit = (int) (xda & 0x01);
                xda >>= 1;
                if (xdabit == 1) {
                    xda ^= xdapoly;
                }
            }
        }
        System.arraycopy(data, 0, crcResult, 0, data.length);
        crcResult[crcResult.length - 2] = (int) (xda & 0xFF);
        crcResult[crcResult.length - 1] = (int) (xda >> 8);
        return crcResult;
    }

    /**
     * 根据byte数组计算CRC16 校验结果（返回10进制数原始报文 + crc16校验的低八位和高八位）
     * 返回结果 原始数据 + 追加CRC16 校验结果
     *
     * @param bytes 参与CRC计算的bytes 注意不包含CRC校验结果
     * @author linanguang
     * @since 2020-05-15 11:02
     */
    public static int[] getCrc16ByByte(byte[] bytes) {
        int[] data = new int[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            //16进制byte转10进制
            data[i] = byteToInt(bytes[i]);
        }
        return getCrc16(data);
    }

    /**
     * byte 转 int
     *
     * @author linanguang
     * @since 2020-05-15 11:07
     */
    public static int byteToInt(byte b) {
        return b & 0xFF;
    }

    /**
     * 根据ascii字符串生成校验码
     *
     * @param str ascii字符串，例如： 011234567890000032005E0082170925165152ST 1234567890 H TT 1709251650 Z 1.700 Q 1.700 VJ 1.700 PT 3.0 PJ 3.0 VT 11.93
     * @return 返回校验码，例如上面的字符串的校验码为：86B5
     * @author wanglei
     * @since 2020/9/27
     */
    public static String genCRC16CodeByAscii(String str) {
        byte[] bytes = str.getBytes();
        int[] crc16ByByte = getCrc16ByByte(bytes);
        int lengthCrc = crc16ByByte.length;
        String s = Integer.toHexString(crc16ByByte[lengthCrc - 1]) + Integer.toHexString(crc16ByByte[lengthCrc - 2]);
        return s.toUpperCase();
    }

    public static void main(String[] args) {
        String asciiStr = "\u0001011234567890000032005E\u00020082170925165152ST 1234567890 H TT 1709251650 Z 1.700 Q 1.700 VJ 1.700 PT 3.0 PJ 3.0 VT 11.93 \u0003";
        String s = genCRC16CodeByAscii(asciiStr);
        System.out.println(s);

        String crc16Str = "B718091800215E3A004276900003114001000000000000000000000000000000000000000000000000000966670000";
        byte[] bytes = HexUtil.hexStringToBytes(crc16Str);
        int i = CRC16_OB(bytes);
        //int类型转换为byte类型 (这里的bytes[0] bytes[1] 就是最终得到的CRC校验码)
        byte[] bytes1 = intTo2Byte(i);
        // byte转换为可查看的String类型
        String str = encodeHexStr(bytes1);
        System.out.println(str);
    }
}
